unit grafpack;
{$F+}
interface

uses
{$IFDEF DPMI}
  Dos,Crt,Graph, WinAPI,Realtype;
{$ELSE}
  Dos,Crt,Graph,Realtype;
{$ENDIF}
type
   D3World=record
           xw1,xw2,yw1,yw2,zw1,zw2:float;
           end;

var
  TheWorld:D3world;
  xwrot,zwrot:integer;
  basex,basey,basez,frontx,fronty,frontz,viewdist:float;
 Graphdriver,Graphmode,XTextglb,YTextglb,VESA16,xw1glb,xw2glb,yw1glb,
 yw2glb:integer;
 charfeedglb,linefeedglb,lineshiftglb:byte;
 Graphmodeglb,VesaGlb:boolean;
 OldOutput : Text;
 xaglb,xscaleglb,yaglb,yscaleglb:float;

Procedure InitGraphic(PathToDriver:string);
{Initializes graphics, Redirects the Write and GoToXY-procedures to
work on the graphics screen.}

Procedure LeaveGraphic;
{Restores Crt-mode, leaves graphics mode. Use if you want to
switch between the two modes in one program. Before final termination
you also have to use the CloseGraph-command from the Graph-Unit.}

Procedure EnterGraphic;
{Switches from Crt-Mode to graphics-mode, InitGraphic must be called
once before.}

procedure GotoXY(X, Y : integer);
{ Set the text position }

procedure setwindow(x1,y1,size:word);
{ Defines drawing area; (x1,y1) is upper left point in *text coordinates*,
 size is the *vertical* extension of the window in textlines. The window
 always comes out square. (Roughly)}

procedure setd3world(x1,y1,z1,x2,y2,z2,vdist:float;xrot,zrot:integer);
{defines what area of the "real" 3-d-world should be drawn, from what
distance it should be viewed(vdist) and what angles the camera has
with the x and z-axes (xrot,zrot). The 3d-world is always mapped into a
cube with length 2 in each direction that the camera moves around of,
looking into the center of the cube. It has a fixed viewing angle
(it's an older model with a fixed focal distance). The cube is then
projected to the window defined by setwindow. All drawing commands
are then in terms of 3-D-world coordinates.}

procedure rotatex(theta:integer);

procedure rotatez(theta:integer);

procedure zoomin;

procedure zoomout;

procedure d3drawpoint(x,y,z:float);

procedure d3line(xl1,yl1,zl1,xl2,yl2,zl2:float);

procedure drawd3axes(c1,c2,c3:string);

{self explaining, the rest}
Implementation

function XTextpixel(Xtextglb:byte):word;
begin
  XTextpixel:=(XTextglb-1)*Charfeedglb;
end;

function YTextpixel(Ytextglb:byte):word;
begin
  YTextpixel:=(YTextglb-1)*linefeedglb+lineshiftglb;
end;

var xchar,ychar:word;

procedure DC(c:byte);
var viewport:viewporttype; x,y:word;
begin
  getviewsettings(viewport);
  x:=xtextpixel(xtextglb); y:=ytextpixel(ytextglb);
  setviewport(x,y,x+xchar,y+ychar,true);
  clearviewport;
  outtextxy(0,0,chr(c));
  with viewport do setviewport(x1,y1,x2,y2,clip);
end;

function WriteGrafChars(var F : TextRec) : integer;
{ Used to output graphics characters through the standard output channel. }
const
  BackSpace = #8;
  LineFeed  = #10;
  Return    = #13;
var
  I : integer;
begin
  with F do
    if Mode = fmOutput then
    begin
      if BufPos > BufEnd then
      begin
        for I := BufEnd to Pred(BufPos) do  { Flush the output buffer }
        begin
          case BufPtr^[I] of
            BackSpace : if XTextGlb > 1 then
                          DEC(XTextGlb);

            LineFeed  : if YTextGlb < 25 then
                          INC(YTextGlb);

            Return    : XTextGlb := 1;
          else
            DC(ORD(BufPtr^[I]));
            if XTextGlb < 80 then
              INC(XTextGlb);
          end; { case }
        end; { for }
      end;
      BufPos := BufEnd;
    end; { if }
  WriteGrafChars := 0;
end; { WriteGrafChars }

function GrafCharZero(var F : TextRec) : integer;
{ Called when standard output is opened and closed }
begin
  GrafCharZero := 0;
end; { GrafCharZero }


procedure GrafCharsON;
{ Redirects standard output to the WriteGrafChars function. }
begin
  Move(Output, OldOutput, SizeOf(Output));  { Save old output channel }
  with TextRec(Output) do
  begin
    OpenFunc:=@GrafCharZero;       { no open necessary }
    InOutFunc:=@WriteGrafChars;    { WriteGrafChars gets called for I/O }
    FlushFunc:=@WriteGrafChars;    { WriteGrafChars flushes automatically }
    CloseFunc:=@GrafCharZero;      { no close necessary }
    Name[0]:=#0;
  end;
end; { GrafCharsON }

procedure GrafCharsOFF;
{ Restores original output I/O channel }
begin
  Move(OldOutput, Output, SizeOf(OldOutput));
end; { GrafCharsOFF }

procedure GotoXY{(X, Y : integer)};
{ Set the text position }
begin
  if (X >= 1) and (X <= 80) and    { Ignore illegal values }
     (Y >= 1) and (Y <= 25) then
  begin
    if GraphModeGlb then
      begin
        XTextGlb := X;      { Set text postion in graphics mode }
        YTextGlb := Y;
      end
    else
      Crt.GotoXY(X, Y);     { Set cursor position in text mode }
  end;
end; { GotoXY }


type
  VgaInfoBlock = record
    VESASignature: array[0..3] of Byte;
    VESAVersion: Word;
    OEMStringPtr: Pointer;
    Capabilities: array[0..3] of Byte;
    VideoModePtr: Pointer;
  end;

const
  VESA16Modes: array[0..2] of Word =
    ($0102, $0104, $0106);

{ Scan the supported mode table for the highest mode this card
  will provide
}

function GetHighestCap(Table: Pointer; Modes: Word; Size: Integer): Integer;
  near; assembler;
asm
        XOR     AX,AX
        LES     DI, Table
@@1:
        MOV     SI, Modes
        ADD     SI, Size
        ADD     SI, Size
        MOV     BX, ES:[DI]
        CMP     BX, 0FFFFH
        JE      @@4
        INC     DI
        INC     DI
        MOV     CX,Size
@@2:
        CMP     BX,[SI]
        JZ      @@3
        DEC     SI
        DEC     SI
        LOOP    @@2
@@3:
        CMP     AX,CX
        JA      @@1
        MOV     AX,CX
        JMP     @@1
@@4:
end;

{$IFDEF DPMI}
type
  TRealRegs = record
    RealEDI, RealESI, RealEBP, Reserved, RealEBX,
    RealEDX, RealECX, RealEAX: Longint;
    RealFlags, RealES, RealDS, RealFS, RealGS,
    RealIP, RealCS, RealSP, RealSS: Word;
  end;

function DetectVesa16: Integer; far; assembler;
var
  Segment, Selector, VesaCap: Word;
asm
{$IFOPT G+}
        PUSH    0000H
        PUSH    0100H
{$ELSE}
        XOR     AX,AX
        PUSH    AX
        INC     AH
        PUSH    AX
{$ENDIF}
        CALL    GlobalDosAlloc
        MOV     Segment,DX
        MOV     Selector,AX
        MOV     DI,OFFSET RealModeRegs
        MOV     WORD PTR [DI].TRealRegs.RealSP, 0
        MOV     WORD PTR [DI].TRealRegs.RealSS, 0
        MOV     WORD PTR [DI].TRealRegs.RealEAX, 4F00H
        MOV     WORD PTR [DI].TRealRegs.RealES, DX
        MOV     WORD PTR [DI].TRealRegs.RealEDI, 0
        MOV     AX,DS
        MOV     ES,AX
        MOV     AX,0300H
        MOV     BX,0010H
        XOR     CX,CX
        INT     31H
        MOV     DI,OFFSET RealModeRegs
        MOV     AX,grError
        PUSH    AX
        CMP     WORD PTR [DI].TRealRegs.RealEAX,004FH
        JNZ     @@Exit
        POP     AX
        MOV     ES,Selector
        XOR     DI,DI
        CMP     ES:[DI].VgaInfoBlock.VESASignature.Word[0], 'EV'
        JNZ     @@Exit
        CMP     ES:[DI].VgaInfoBlock.VESASignature.Word[2], 'AS'
        JNZ     @@Exit
        MOV     AX,0000
        MOV     CX,1
        INT     31H
        MOV     VesaCap,AX
        MOV     DX,ES:[DI].VgaInfoBlock.VideoModePtr.Word[2]
        MOV     CX,4
        XOR     AX,AX
@@Convert:
        SHL     DX,1
        RCL     AX,1
        LOOP    @@Convert
        ADD     DX,ES:[DI].VgaInfoBlock.VideoModePtr.Word[0]
        ADC     AX,0
        MOV     CX,AX
        MOV     BX,VesaCap
        MOV     AX,0007H
        INT     31H
        INC     AX
        XOR     CX,CX
        MOV     DX,0FFFFH
        INT     31H
        MOV     ES,BX
        PUSH    ES
        PUSH    DI
{$IFOPT G+}
        PUSH    OFFSET Vesa16Modes
        PUSH    0003H
{$ELSE}
        MOV     SI, OFFSET Vesa16Modes
        PUSH    SI
        MOV     AX, 5
        PUSH    AX
{$ENDIF}
        CALL    GetHighestCap
        PUSH    AX
        MOV     BX,VesaCap
        MOV     AX,0001H
        INT     31H
@@Exit:
        PUSH    Selector
        CALL    GlobalDosFree
        POP     AX
end;
{$ELSE}
function DetectVesa16: Integer; far; assembler;
var
  VesaInfo: array[0..255] of Byte;
asm
        MOV     AX,SS
        MOV     ES,AX
        LEA     DI,VesaInfo
        MOV     AX,4F00H
        INT     10H
        CMP     AX,004FH
        MOV     AX,grError
        JNZ     @@Exit
        CMP     ES:[DI].VgaInfoBlock.VESASignature.Word[0], 'EV'
        JNZ     @@Exit
        CMP     ES:[DI].VgaInfoBlock.VESASignature.Word[2], 'AS'
        JNZ     @@Exit
        LES     DI,ES:[DI].VgaInfoBlock.VideoModePtr
        PUSH    ES
        PUSH    DI
        MOV     AX, OFFSET Vesa16Modes
        PUSH    AX
        MOV     AX,3
        PUSH    AX
        CALL    GetHighestCap
@@Exit:
end;
{$ENDIF}

procedure initgraphic;
var error:word;
begin
  vesaglb:=false;
  VESA16:=InstallUserDriver('VESA16',@DetectVesa16);
  if GraphResult<>0 then begin
    writeln('Error installing Vesa16'); end;
  GraphDriver := Detect;
  InitGraph(GraphDriver, GraphMode, pathtodriver);
  if GraphResult <> grOk then
  begin
    Writeln('Graphics init error: ', GraphErrorMsg(GraphDriver));
    Halt(1);
  end;
  xchar:=7; ychar:=7;
  Case Graphdriver of
    CGA:begin Charfeedglb:=8; Linefeedglb:=8; Lineshiftglb:=0;
              end;
    EGA:begin Charfeedglb:=8;  Linefeedglb:=14;
               lineshiftglb:=3;
               end;
    EGA64:begin Charfeedglb:=8;  Linefeedglb:=14;
                 LineshiftGlb:=3;
                 end;
    EGAMono:begin Charfeedglb:=8;  Linefeedglb:=14;
                   Lineshiftglb:=3;
                   end;
    HercMono:begin Charfeedglb:=9;  Linefeedglb:=14;
                   LineshiftGlb:=3;
                   end;
    VGA:begin Charfeedglb:=8;  Linefeedglb:=19;
              Lineshiftglb:=3;
              end;
  else
    begin
      vesaglb:=true;
      graphmode:=0;
      setgraphmode(graphmode);
      charfeedglb:=10; linefeedglb:=24;
      lineshiftglb:=8; xchar:=7; ychar:=7;
    end;
  end;
  Graphmodeglb:=true;
  Grafcharson;
end;

procedure leavegraphic;
begin
  RestoreCrtMode;
  GraphmodeGlb:=false;
  Grafcharsoff;
end;

procedure entergraphic;
begin
  SetGraphmode(graphmode);
  Graphmodeglb:=true;
  GrafCharsOn;
end;

procedure setwindow;
begin
  xw1glb:=(x1-1)*charfeedglb; yw1glb:=(y1-1)*linefeedglb;
  yw2glb:= yw1glb+size*linefeedglb;
  xw2glb:=xw1glb+round(0.75*getmaxx/getmaxy*(yw2glb-yw1glb));
  setviewport(xw1glb,yw1glb,xw2glb,yw2glb,true);
end;

var thetax,thetaz,sinx,sinz,cosx,cosz:float;
    rightx, rightz:integer;

procedure makeradians;
begin
  thetax:=2*pi*xwrot/360; thetaz:=2*pi*zwrot/360;
  sinx:=sin(thetax); cosx:=cos(thetax);
  sinz:=sin(thetaz); cosz:= cos(thetaz);
  rightx:=(xwrot+90) mod 180;  rightz:=zwrot mod 180;
end;

function scalar(xb,yb,zb:float):float;
begin
  scalar:=yb*sinx*sinz+zb*cosz+xb*sinz*cosx;
end;

procedure initworld;
var umin,umax,vmin,vmax,d2world:float;
    i,j,k:integer;
begin
  makeradians;
  if viewdist<0 then viewdist:=0.00001;
  d2world:=0.25;
  xaglb:=(xw2glb-xw1glb)/2;
  xscaleglb:=(xw2glb-xw1glb)/2/d2world;
  yaglb:=(yw2glb-yw1glb)/2;
  yscaleglb:=(yw2glb-yw1glb)/2/d2world;
end;

procedure setd3world(x1,y1,z1,x2,y2,z2,vdist:float;xrot,zrot:integer);
var d:float;
begin
  with theworld do
  begin
    xw1:=x1;  xw2:=x2;  yw1:=y1; yw2:=y2; zw1:=z1;  zw2:=z2;
  end;
  zwrot:=zrot; xwrot:=xrot;  viewdist:=vdist;
  initworld;
end;

procedure rotatez(theta:integer);
begin
    zwrot:=zwrot+theta;  initworld;
end;

procedure rotatex(theta:integer);
begin
    xwrot:=xwrot+theta;  initworld;
end;

procedure zoomin;
var v:float;
begin
  viewdist:=viewdist-0.05; initworld;
end;

procedure zoomout;
begin
  viewdist:=viewdist+0.05; initworld;
end;

procedure blockx(x:float;var xb:float);
begin
  with TheWorld do
  xb:= -1+2*(x-xw1)/(xw2-xw1);
end;
procedure blocky(y:float;var yb:float);
begin
  with TheWorld do
  yb:= -1+2*(y-yw1)/(yw2-yw1);
end;
procedure blockz(z:float;var zb:float);
begin
  with TheWorld do
  zb:= -1+2*(z-zw1)/(zw2-zw1);
end;

procedure project(xb,yb,zb:float; var u,v:float;var visible:boolean);
var scal,d:float;
begin
  scal:=scalar(xb,yb,zb);
  d:=viewdist-scal;
  if d<=0.1 then visible:=false else
  begin
    if rightz<>0 then
      v:=(zb-scal*cosz)/sinz
    else
      v:=-(yb*sinx+xb*cosx)/cosz;
    if rightx<>0 then
      u:=(yb+sinx*(v*cosz-scal*sinz))/cosx
    else
      u:=-xb*sinx;
    u:=u/d;
    v:=v/d;
    visible:=(abs(u)<10) and (abs(v)<10);
  end;
end;

procedure d3window(x,y,z:float; var xs,ys:integer;var visible:boolean);
var xb,yb,zb,scal,d,u,v:float;
begin
  blockx(x,xb);  blocky(y,yb);  blockz(z,zb);
  project(xb,yb,zb,u,v,visible);
  if visible then
  begin
    xs:=round(u*xscaleglb+xaglb); ys:=round(yaglb-v*yscaleglb);
  end;
end;


procedure d3drawpoint(x,y,z:float);
var xs,ys:integer; var visible:boolean;
begin
  d3window(x,y,z,xs,ys,visible);
  if visible then  putpixel(xs,ys,getcolor);
end;

procedure d3line(xl1,yl1,zl1,xl2,yl2,zl2:float);
var u1,v1,u2,v2:integer; var visible:boolean;
begin
  d3window(xl1,yl1,zl1,u1,v1,visible);
  if visible then
  begin
    d3window(xl2,yl2,zl2,u2,v2,visible);
    if visible then  line(u1,v1,u2,v2);
  end;
end;

procedure drawd3axes(c1,c2,c3:string);

procedure drawoneaxis(x1,y1,z1,x2,y2,z2:float;c:string);
var norms,wx,wy:float;  visible:boolean;
    xs1,ys1,xs2,ys2:integer; vsx,vsy:float;
begin
  d3line(x1,y1,z1,x2,y2,z2);
  d3window(x1,y1,z1,xs1,ys1,visible);
  if visible then
  begin
  d3window(x2,y2,z2,xs2,ys2,visible);
  if visible then
  begin
  vsx:=(xs2-xs1); vsy:=(ys2-ys1);
  norms:=sqrt(vsx*vsx+vsy*vsy);
  if norms>0 then
  begin
    vsx:=vsx/norms; vsy:=vsy/norms;
    wx:=(-vsx+vsy)/sqrt(2); wy:=(-vsy-vsx)/sqrt(2);
    line(xs2,ys2,xs2+round(5*wx),ys2+round(5*wy));
    wx:=(-vsx-vsy)/sqrt(2); wy:=(-vsy+vsx)/sqrt(2);
    line(xs2,ys2,xs2+round(5*wx),ys2+round(5*wy));
    moveto(xs2-10,ys2-10);
    outtext(c);
  end;
  end;
  end;
end;

begin   {******* drawd3axes ******}
  with TheWorld do
  begin
    drawoneaxis(xw1,yw1,zw1,xw2,yw1,zw1,c1);
    drawoneaxis(xw1,yw1,zw1,xw1,yw2,zw1,c2);
    drawoneaxis(xw1,yw1,zw1,xw1,yw1,zw2,c3);
  end;
end;
end.